/// ------------------------------------------------------------------------------------------------------------------------------------
///
/// MIT License
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
///
/// Copyright (c) 2024 ycz. All rights reserved.
///
/// Created by ycz on 2024/1/2.
///
/// @file  y_ota.c
///
/// @brief
///     y_ota 是一种用于嵌入式的简单 ota 库
///
/// ------------------------------------------------------------------------------------------------------------------------------------



/// ------------------------------------------------------------------------------------------------------------------------------------
/// 头文件
/// ------------------------------------------------------------------------------------------------------------------------------------

#include "y_ota.h"

#include <string.h>
#include "y_sa.h"



/// ------------------------------------------------------------------------------------------------------------------------------------
/// 宏定义
/// ------------------------------------------------------------------------------------------------------------------------------------

#define Y_OTA_APP_ADDR_MIAN Y_LL_OTA_ADDR_MIAN  ///< 主分区固件存放地址
#define Y_OTA_APP_ADDR_BAK  Y_LL_OTA_ADDR_BAK   ///< 备份分区固件存放地址



/// ------------------------------------------------------------------------------------------------------------------------------------
/// 全局变量
/// ------------------------------------------------------------------------------------------------------------------------------------

static OTA_CONFIG_st g_config;  ///< 全局参数



/// ------------------------------------------------------------------------------------------------------------------------------------
/// 私有函数
/// ------------------------------------------------------------------------------------------------------------------------------------

/// @brief   获取 crc32 校验值
/// @param   [in] app                      固件参数
/// @return  crc32
static uint32_t _y_ota_get_crc32(uint32_t addr, uint32_t size) {

    uint32_t calc_crc32 = 0xFFFFFFFF;
    uint32_t app_size   = size;
    uint32_t app_addr   = addr;

    // 计算 crc32
    y_utils_crc32_restart();
    while (app_size) {
        uint8_t  read_buf[4096] = {0};
        uint32_t read_size      = app_size >= 4096 ? 4096 : app_size;
        y_ll_flash_read(app_addr, read_buf, read_size);
        calc_crc32 = y_utils_crc32_calc(read_buf, read_size);
        app_addr   = app_addr + read_size;
        app_size   = app_size >= 4096 ? app_size - 4096 : 0;
    }

    return calc_crc32;
}

/// @brief   分区固件复制 app2 复制到 app1
/// @param   [in] app1                     固件1参数
/// @param   [in] app2                     固件2参数
/// @retval  true                          成功
/// @retval  false                         失败
static bool _y_ota_copy(OTA_INFO_st *app1, OTA_INFO_st *app2) {

    // 断言
    if (app2->size == 0) {
        return false;
    }

    // 判断 app2 是否完整
    if (_y_ota_get_crc32(app2->addr, app2->size) != app2->crc32) {
        return false;
    }

    // 开始复制 尝试 3 次
    for (int i = 0; i < 3; ++i) {

        uint32_t app_size  = app2->size;
        uint32_t app1_addr = app1->addr;
        uint32_t app2_addr = app2->addr;
        while (app_size) {
            uint8_t  read_buf[4096] = {0};
            uint32_t read_size      = app_size >= 4096 ? 4096 : app_size;
            if (y_ll_flash_read(app2_addr, read_buf, read_size) != read_size) {
                continue;
            }
            if (y_ll_flash_erase(app1_addr, 4096) == false) {
                continue;
            }
            if (y_ll_flash_write(app1_addr, read_buf, read_size) != read_size) {
                continue;
            }
            app_size  = app_size >= 4096 ? app_size - 4096 : 0;
            app1_addr = app1_addr + read_size;
            app2_addr = app2_addr + read_size;
        }

        // 判断 app1 升级后是否完整
        app1->size  = app2->size;
        app1->crc32 = app2->crc32;
        if (_y_ota_get_crc32(app1->addr, app1->size) == app1->crc32) {
            app1->ver = app2->ver;  // 复制成功 才给新的版本号
            y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);
            return true;
        }
    }

    return false;  // 复制失败
}



/// ------------------------------------------------------------------------------------------------------------------------------------
/// 公有函数
/// ------------------------------------------------------------------------------------------------------------------------------------

/// @brief   打印 y_ota 版本信息
void y_ota_print_version() {
    YLOG_VERSION("y_ota", Y_OTA_MAJOR, Y_OTA_MINOR, Y_OTA_PATCH);
}


/// @brief   自动选择启动位置
void y_ota_auto_switch() {

    // 判断是否需要升级
    if (y_utils_cmp_version(g_config.app_main.ver, g_config.app_bak.ver) < 0) {
        // 有新的升级包  将新的升级包从备份分区搬运到主分区
        if (_y_ota_copy(&g_config.app_main, &g_config.app_bak)) {
            YLOGI("ota app copy  BAK --> MAIN success");  // 升级成功
        } else {
            YLOGE("ota app copy  BAK --> MAIN fail");  // 升级失败
        }
    } else {
        // 检验主分区
        if (_y_ota_get_crc32(g_config.app_main.addr, g_config.app_main.size) != g_config.app_main.crc32
            && _y_ota_get_crc32(g_config.app_bak.addr, g_config.app_bak.size) == g_config.app_bak.crc32) {  // 主分区损坏 备份分区完整
            // 比较两个分区版本号
            if (y_utils_cmp_version(g_config.app_main.ver, g_config.app_bak.ver) <= 0) {
                // 主分区固件损坏 从备份分区复制一份
                YLOGE("app MAIN error,  try use app BAK,  CRC32  0X%X", _y_ota_get_crc32(g_config.app_main.addr, g_config.app_main.size));
                if (_y_ota_copy(&g_config.app_main, &g_config.app_bak) == false) {
                    YLOGE("ota app copy  BAK --> MAIN fail");  // 升级失败
                }
            }
        }
    }

    // 默认跳到主分区
    YLOGI("Start APP main ...");
    y_ll_jump_addr(g_config.app_main.addr);
}

/// @brief   写入升级包
/// @param   [in] pack                     升级包
/// @return  升级进度
uint8_t y_ota_upgrade(OTA_PACK_st pack) {

    // 判断是否需要升级
    if (y_utils_cmp_version(pack.ver, y_sys_get_software_version()) <= 0) {
        return 0;  // 无需升级
    }
    // 判断偏移地址是否在范围内 判断数据大小是否正常 判断数据检验是否正常
    if (pack.offset > 256 * 1024 || pack.size > 4096 || pack.sum != y_utils_get_sum8(pack.data, pack.size, true)) {
        return 0;  // 偏移地址错误 或 数据太大 最大 4096 或 校验失败
    }

    static uint16_t pack_num      = 0;
    static uint16_t last_pack_num = 0;

    // 第一包数据 清空之前的 备份区的 ota 信息
    if (pack.offset == 0) {
        memset(&g_config.app_bak, 0, sizeof(g_config.app_bak));
        g_config.app_bak.addr = Y_OTA_APP_ADDR_BAK;
        y_ota_config_set(&g_config);
        YLOGI("OTA upgrade start");
        pack_num      = 0;
        last_pack_num = 0;
    }
    pack_num++;

    // 判断有没有重复 或者丢包
    uint16_t now_pack_num = (pack.offset + pack.size) / pack.size;
    if (now_pack_num == last_pack_num) {
        // YLOGI("pack repeat  %d", now_pack_num);
    } else if (now_pack_num - last_pack_num != 1 && pack.offset + pack.size != pack.total_size) {
        YLOGI("pack loss  now %d  last %d", now_pack_num, last_pack_num);
    }
    last_pack_num = now_pack_num;


    // 写入数据 先读再写 在读再校验
    for (int i = 0; i < 3; ++i) {
        uint8_t buf[8192] = {0};
        y_ll_flash_read(g_config.app_bak.addr + pack.offset / 4096 * 4096, buf, sizeof(buf));
        y_ll_flash_erase(g_config.app_bak.addr + pack.offset / 4096 * 4096, sizeof(buf));
        memcpy(&buf[pack.offset % 4096], pack.data, pack.size);
        uint8_t sum = y_utils_get_sum8(buf, sizeof(buf), true);
        y_ll_flash_write(g_config.app_bak.addr + pack.offset / 4096 * 4096, buf, sizeof(buf));
        y_ll_flash_read(g_config.app_bak.addr + pack.offset / 4096 * 4096, buf, sizeof(buf));
        if (sum == y_utils_get_sum8(buf, sizeof(buf), true)) {
            break;
        }
        YLOGE("sum error  %d", pack.offset);
    }

    // 判断数据包类型 最后一包数据
    if (pack.offset + pack.size == pack.total_size) {  // 判断升级是否成功
        last_pack_num  = 0;
        uint32_t crc32 = _y_ota_get_crc32(g_config.app_bak.addr, pack.total_size);
        if (crc32 != pack.crc32) {
            YLOGE("OTA upgrade V%d.%d.%d fail!  crc32 0X%x pack_num %d", pack.ver.major, pack.ver.minor, pack.ver.patch, crc32, pack_num);
            return 0;  // 升级失败
        }
        // 升级成功
        g_config.app_bak.ver   = pack.ver;
        g_config.app_bak.addr  = Y_OTA_APP_ADDR_BAK;
        g_config.app_bak.size  = pack.total_size;
        g_config.app_bak.crc32 = pack.crc32;
        y_ota_config_set(&g_config);
        YLOGI("OTA upgrade V%d.%d.%d success!  pack_num %d", pack.ver.major, pack.ver.minor, pack.ver.patch, pack_num);
        return 100;
    }
    return (pack.offset + pack.size * 1.0 / pack.total_size * 100) >= 100 ? 99 : (uint8_t) (pack.offset * 1.0 / pack.total_size * 100);  // 当前进度
}

/// @brief   打印参数
/// @param   [in] config                   参数
void y_ota_config_print(OTA_CONFIG_st *config) {
    YLOGA(config);
    YLOGI("OTA ver                      main : V%d.%d.%d            bak : V%d.%d.%d    ",
          config->app_main.ver.major,
          config->app_main.ver.minor,
          config->app_main.ver.patch,
          config->app_bak.ver.major,
          config->app_bak.ver.minor,
          config->app_bak.ver.patch);
    YLOGI("OTA addr                     main : 0X%-8X        bak : 0X%-8X", config->app_main.addr, config->app_bak.addr);
    YLOGI("OTA size                     main : %-6d            bak : %-6d    ", config->app_main.size, config->app_bak.size);
    YLOGI("OTA crc32                    main : 0X%-8X        bak : 0X%-8X", config->app_main.crc32, config->app_bak.crc32);
}

/// @brief   参数获取
/// @return  参数值
OTA_CONFIG_st *y_ota_config_get() {
    return &g_config;
}

/// @brief   参数设置
/// @param   [in] config                   参数
/// @retval  true                          成功
/// @retval  false                         失败
bool y_ota_config_set(OTA_CONFIG_st *config) {
    // 断言
    YLOGA_FALSE(config);
    memcpy(&g_config, config, sizeof(g_config));  // 参数赋值
    return y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);
}

/// @brief    boot 应用初始化调用此函数
void y_ota_boot_init() {

    // 加载参数
    uint16_t size = 0;
    if (y_nvs_get((uint8_t *) "ota_config", (uint8_t *) &g_config, &size) == false || size != sizeof(g_config)) {  // 从 nvs 读取失败
        y_nvs_reset();                                                                                             // 重置 nvs
        memset(&g_config, 0, sizeof(g_config));
        g_config.app_main.addr = Y_OTA_APP_ADDR_MIAN;
        g_config.app_bak.addr  = Y_OTA_APP_ADDR_BAK;
        y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);
    }
    if (g_config.app_main.addr == 0) {
        g_config.app_main.addr = Y_OTA_APP_ADDR_MIAN;
    }
    if (g_config.app_bak.addr == 0) {
        g_config.app_bak.addr = Y_OTA_APP_ADDR_BAK;
    }
    y_ota_config_print(&g_config);  // 打印参数
}

/// @brief   初始化
void y_ota_init() {

    // 加载算法参数
    uint16_t size = 0;
    if (y_nvs_get((uint8_t *) "ota_config", (uint8_t *) &g_config, &size) == false || size != sizeof(g_config)) {  // 从 nvs 读取失败
        memset(&g_config, 0, sizeof(g_config));
        g_config.app_main.addr = Y_OTA_APP_ADDR_MIAN;
        g_config.app_bak.addr  = Y_OTA_APP_ADDR_BAK;
        y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);
    }
    if (g_config.app_main.addr == 0) {
        g_config.app_main.addr = Y_OTA_APP_ADDR_MIAN;
    }
    if (g_config.app_bak.addr == 0) {
        g_config.app_bak.addr = Y_OTA_APP_ADDR_BAK;
    }

    // 判断有没有 ota 信息 没有则计算一个默认信息
    uint32_t calc_crc32 = _y_ota_get_crc32(g_config.app_main.addr, g_config.app_main.size);
    if (g_config.app_main.crc32 != calc_crc32) {
        g_config.app_main.ver   = y_sys_get_software_version();                              // 软件版本
        g_config.app_main.size  = 256 * 1024;                                                // 默认256k
        g_config.app_main.crc32 = calc_crc32;                                                // crc32
        y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);  // 更新设置
    } else if (y_utils_cmp_version(g_config.app_main.ver, y_sys_get_software_version()) != 0) {
        // 固件版本与实际版本不相同
        g_config.app_main.ver = y_sys_get_software_version();                                // 软件版本
        y_nvs_set((uint8_t *) "ota_config", (uint8_t *) &g_config, sizeof(g_config), true);  // 更新设置
    }

    y_ota_config_print(&g_config);  // 打印参数
}